﻿#region

using System;
using System.Net;
using System.Net.Sockets;
using Lunexx;
#endregion

namespace Lunexx.Network
{

    #region Delegates

    public delegate void DestroyDelegate(Client client);

    #endregion

    public abstract class Client : IDisposable
    {
        #region Properties

        internal Socket ClientSocket
        {
            get { return m_clientSocket; }
            set
            {
                if (m_clientSocket != null)
                {
                    m_clientSocket.Close();
                }
                m_clientSocket = value;
            }
        }

        internal IPEndPoint Server { get; set; }

        internal Socket ServerSocket
        {
            get { return m_serverSocket; }
            set
            {
                if (m_serverSocket != null)
                {
                    m_serverSocket.Close();
                }
                m_serverSocket = value;
            }
        }

        protected byte[] Buffer
        {
            get { return m_buffer; }
        }

        protected byte[] ServerBuffer
        {
            get { return m_serverBuffer; }
        }

        #endregion

        #region Private Fields

        private readonly DestroyDelegate m_destroyer;
        private readonly byte[] m_buffer = new byte[4096];
        private readonly byte[] m_serverBuffer = new byte[1024];
        private Socket m_clientSocket;
        private Socket m_serverSocket;

        #endregion

        #region Constructors & Decontructors

        protected Client(Socket clientSocket, DestroyDelegate destroyer, IPEndPoint server)
        {
            ClientSocket = clientSocket;
            m_destroyer = destroyer;
            Server = server;
        }
/*
        protected Client()
        {
            ClientSocket = null;
            m_destroyer = null;
        }
*/
        #endregion

        #region Methods

        #region IDisposable Members

        public void Dispose()
        {
            InfoWrite("Shutting down: " + this.ToString(),3);
            try
            {

                ClientSocket.Shutdown(SocketShutdown.Both);
            }

            catch (Exception e)
            {
                ConsoleWrapper.DebugWrite(e.ToString(),1);
            }

            try
            {
                ServerSocket.Shutdown(SocketShutdown.Both);
            }

            catch (Exception e)
            {
                ConsoleWrapper.DebugWrite(e.ToString(), 1);
            }

            if (ClientSocket != null)
            {
                ClientSocket.Close();
            }
            if (ServerSocket != null)
            {
                ServerSocket.Close();
            }
            ClientSocket = null;
            ServerSocket = null;
            if (m_destroyer != null)
            {
                m_destroyer(this);
            }
        }

        #endregion
        /// <summary>
        /// Connect to server.
        /// </summary>
        public void StartHandshake()
        {
            try
            {
                InfoWrite("Connecting to server at " + Server.Address.ToString() + ":" + Server.Port.ToString(), 2);
                ServerSocket = new Socket(Server.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                ServerSocket.BeginConnect(Server, OnConnected, ServerSocket);
            }
            catch (Exception e)
            {
                Dispose();
                ConsoleWrapper.DebugWrite(e.ToString(),1);
                
            }
        }
        /// <summary>
        /// Called when connected to server.
        /// </summary>
        /// <param name="ar"></param>
        private void OnConnected(IAsyncResult ar)
        {
            try
            {
                InfoWrite("Connected to: " + Server.Address.ToString() + ":" + Server.Port.ToString() + " beginning relay.",2);
                ServerSocket.EndConnect(ar);
                StartRelay();
            }
            catch (Exception e)
            {
                Dispose();
                ConsoleWrapper.DebugWrite(e.ToString(), 1);

            }
        }

        public void OnClientReceive(IAsyncResult asResult)
        {
            try
            {
                InfoWrite("Receiving data from " + ClientString(),3);
                int length = ClientSocket.EndReceive(asResult);
                if (length <= 0)
                {
                    Dispose();
                    return;
                }

                ServerSocket.BeginSend(Buffer, 0, length, SocketFlags.None, new AsyncCallback(OnRemoteSent),
                                       ServerSocket);
            }
                
            catch (Exception e)
            {
                Dispose();
                ConsoleWrapper.DebugWrite(e.ToString(), 1);

            }
        }

        protected void OnRemoteSent(IAsyncResult asResult)
        {
            try
            {
                int length = ServerSocket.EndSend(asResult);
                if (length > 0)
                {
                    ClientSocket.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None,
                                              new AsyncCallback(OnClientReceive), ClientSocket);
                    return;
                }
            }

                
            catch (Exception e)
            {
                Dispose();
                ConsoleWrapper.DebugWrite(e.ToString(), 1);

            }
        }

        protected void OnRemoteReceive(IAsyncResult asResult)
        {
            try
            {
                InfoWrite("Receiving data from " + ServerString(),3);
                int length = ServerSocket.EndReceive(asResult);
                if (length <= 0)
                {
                    Dispose();
                }

                ClientSocket.BeginSend(ServerBuffer, 0, length, SocketFlags.None, new AsyncCallback(OnRemoteSent),
                                       ClientSocket);

            }
            catch (Exception e)
            {
                Dispose();
                ConsoleWrapper.DebugWrite(e.ToString(), 1);

            }
        }

        protected void OnClientSent(IAsyncResult asResult)
        {
            try
            {
                int length = ClientSocket.EndSend(asResult);
                if (length > 0)
                {
                    ServerSocket.BeginReceive(ServerBuffer, 0, length, SocketFlags.None,
                                              new AsyncCallback(OnRemoteReceive), ServerSocket);
                    return;
                }
            }
            catch (Exception e)
            {
                Dispose();
                ConsoleWrapper.DebugWrite(e.ToString(), 1);

            }
        }

        /// <summary>
        /// Start relaying data between client and server.
        /// </summary>
        public void StartRelay()
        {
            try
            {
                ClientSocket.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(OnClientReceive),
                                          ClientSocket);
                ServerSocket.BeginReceive(ServerBuffer, 0, ServerBuffer.Length, SocketFlags.None,
                                          new AsyncCallback(OnRemoteReceive), ServerSocket);
            }
            catch (Exception e)
            {
                Dispose();
                ConsoleWrapper.DebugWrite(e.ToString(), 1);

            }
        }
        /// <summary>
        /// Returns server infomation as string
        /// </summary>
        public string ServerString()
        {
            return Server.Address + ":" + Server.Port;
        }
        public string ClientString()
        {
            return ((IPEndPoint) ClientSocket.RemoteEndPoint).Address.ToString() + ":" +
                   ((IPEndPoint) ClientSocket.RemoteEndPoint).Port.ToString();
        }
        public override string ToString()
        {
            try
            {
                return ((IPEndPoint) ClientSocket.RemoteEndPoint).Address.ToString() + ":" +
                       ((IPEndPoint) ClientSocket.RemoteEndPoint).Port.ToString() + " --> " + Server.Address.ToString() +
                       ":" + Server.Port;
            }
            catch
            {

                return "Incoming connection";
            }
        }

        public abstract void InfoWrite(string input, byte loglevel);
    }

    #endregion
}